﻿#region Copyright © 2010 Chad Albrecht & Mark Adams
/*
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.*/

/* © BLOKUS and associated trademarks are copyright Mattel, Inc.   */
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

namespace Blokus
{ 
   	public class Piece : UserControl
    {
        #region Constants
        private const double TILE_ELEMENT_SIZE_IN_XAML = 7.5;
        private const string TILE_ELEMENT_RESOURCE = "Blokus.rect.xaml";

        private const double MOVE_OPACITY = 0.5;
        private const double NORMAL_OPACITY = 1.0;

        private const int TILE_ZINDEX = 20;
       
        #endregion

        #region Events
        public delegate void PiecePlacedHandler(object sender);
        public event PiecePlacedHandler PiecePlaced;
        #endregion

        #region Fields
        private List<Canvas> _Tiles = new List<Canvas>();

        private static string _SingleTileXAML = string.Empty;
        
		private Canvas graphic;
        private TransformGroup base_transforms = null;

        private Point _lastMousePositionGlobal;
        private Point _lastMousePositionLocal; 

        private bool isMouseCaptured = false;
        private double mouseVerticalPosition = double.NaN;
        private double mouseHorizontalPosition = double.NaN;
        private double mouseVerticalDropPosition = double.NaN;
        #endregion

        #region Properties
        private string _name = "piece";
        public string Name
        {
            get { return _name; }           
        }

        private int _nFlips = 0;
        public int NFlips
        {
            get { return _nFlips; }            
        }

        private int _nRotations = 0;
        public int NRotations
        {
            get { return _nRotations; }            
        }

        private List<Offset> _OriginalVectors = new List<Offset>();
        public List<Offset> OriginalVectors
        {
            get { return _OriginalVectors; }            
        }

        private List<Offset> _CurrentVectors = new List<Offset>();
        public List<Offset> CurrentVectors
        {
            get { return _CurrentVectors; }
        }

        private Color _color = Colors.Red;
        public Color Color
        {
            get { return _color; }
            set { _color = value; }
        }

        private double _tileSize = 50.0;
        public double TileSize
        {
            get { return _tileSize; }
            set { _tileSize = value; }
        }

        private GameBoard _gameBoard = null;
        public GameBoard gameBoard
        {
            get { return _gameBoard; }
            set { _gameBoard = value; }
        }
        
        public int TileCount
        {
            get { return _Tiles.Count; }            
        }

        private int _playerNumber = 0;
        public int PlayerNumber
        {
            get { return _playerNumber; }
            set { _playerNumber = value; }
        }

        private double _currentRotationAngle = 0;
        public double CurrentRotationAngle
        {
            get { return _currentRotationAngle; }            
        }

        private TileWell _parent = null;
        public TileWell Parent
        {
            get { return _parent; }           
        }

        private Point _origTopLeft;
        public Point OrigTopLeft
        {
            get { return _origTopLeft; }
            set { _origTopLeft = value; }
        }

        private bool _placed = false;
        public bool Placed
        {
            get { return _placed; }
            set { _placed = value; }
        }

        #endregion

        #region ctor
        public Piece(Color color, TileWell parent, string name, int rotations, int flips)
        {
            Stream stream = this.GetType().Assembly.GetManifestResourceStream(TILE_ELEMENT_RESOURCE);
            StreamReader reader = new StreamReader(stream);
            
            if (_SingleTileXAML == string.Empty)
            {
                _SingleTileXAML = reader.ReadToEnd();
            }
            
            this.Color = color;
            this.graphic = new Canvas();            
            this.Content = this.graphic;
            this.IsHitTestVisible = true;
            this._parent = parent;
            this._name = name;
            this._nFlips = flips;
            this._nRotations = rotations;
            
            //ToolTipService.SetToolTip(this.graphic, name);            
        }
        #endregion       

        protected void AddTile(int x, int y)
        {            
            OriginalVectors.Add(new Offset(x, y));
            CurrentVectors.Add(new Offset(x, y));
            Canvas tile = (Canvas)XamlReader.Load(_SingleTileXAML);
            this.graphic.Children.Add(tile);
            
            tile.SetValue(Canvas.TopProperty, (double)(y * TILE_ELEMENT_SIZE_IN_XAML));
            tile.SetValue(Canvas.LeftProperty, (double)(x * TILE_ELEMENT_SIZE_IN_XAML));
            this.SetValue(Canvas.ZIndexProperty, TILE_ZINDEX);
        }

        public void InitTiles()
        {
            ClearTransforms();              
            
            _Tiles.Clear();
            foreach (UIElement elem in this.graphic.Children)
            {
                Canvas canvas = (Canvas)elem;
                Rectangle tile = (Rectangle)canvas.Children[0];                
                tile.Fill = new SolidColorBrush(this.Color);
                
                _Tiles.Add(canvas);
            }
            //System.Diagnostics.Debug.WriteLine(String.Format("This piece has {0} tiles in it", this.TileCount));
            this.Placed = false;
            ClearTransforms();              
        }

        public void RedrawTiles()
        {            
            for(int i=0; i<_Tiles.Count; i++)
            {
                Canvas tile = _Tiles[i];
                Offset v = CurrentVectors[i];
                tile.SetValue(Canvas.TopProperty, (double)(v.Y * TILE_ELEMENT_SIZE_IN_XAML));
                tile.SetValue(Canvas.LeftProperty, (double)(v.X * TILE_ELEMENT_SIZE_IN_XAML));
            }
        }

        #region Key Events

        public void KeyPress(KeyEventArgs e)
        {
            base.OnKeyDown(e);
            //System.Diagnostics.Debug.WriteLine(String.Format("{0} key pressed.", e.Key));           
           
            if ((e.Key == Key.X) || (e.Key == Key.Left))
            {
                FlipPieceX();
                e.Handled = true;
            }
            else if ((e.Key == Key.S) || (e.Key == Key.Right))
            {
                FlipPieceY();
                e.Handled = true;
            }
            else if ((e.Key == Key.A) || (e.Key == Key.Up))
            {
                RotatePieceCCW();
                e.Handled = true;
            }
            else if ((e.Key == Key.Z) || (e.Key == Key.Down))
            {
                RotatePieceCW();
                e.Handled = true;
            }     
        }

        #endregion

        #region Mouse Events

        protected override void OnMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);

            if(!this.Placed && this.Parent.IsTurn)
                PickupPiece(e);

            e.Handled = true;
        }     

        protected override void OnMouseLeftButtonUp(System.Windows.Input.MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);

            if (!this.Placed && this.Parent.IsTurn)
                DropPiece(e);

            e.Handled = true;
        }

        protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e)
        {
            base.OnMouseMove(e);
            this._lastMousePositionGlobal = e.GetPosition(null);
            this._lastMousePositionLocal = e.GetPosition(this);

            TrackMouse(e);

            //System.Diagnostics.Debug.WriteLine("<piece>");
            //foreach (Canvas tile in this._Tiles)
            //{                
            //    System.Diagnostics.Debug.WriteLine(String.Format("tile @ top:{0} left:{1}", tile.GetValue(Canvas.TopProperty), tile.GetValue(Canvas.LeftProperty)));                               
                
            //}
            //System.Diagnostics.Debug.WriteLine("</piece>");
        }                

        protected override void OnMouseWheel(MouseWheelEventArgs e)
        {
            base.OnMouseWheel(e);
            if (isMouseCaptured)
            {     
                if(e.Delta > 0)
                    RotatePieceCW();  
                else
                    RotatePieceCCW();
                e.Handled = true;
            }
        }
        #endregion

        #region Piece Movement Handlers
        private void TrackMouse(System.Windows.Input.MouseEventArgs e)
        {
            if (isMouseCaptured)
            {
                Point MouseLoc = e.GetPosition(null);

                // Calculate the current position of the object.
                double deltaV = MouseLoc.Y - mouseVerticalPosition;
                double deltaH = MouseLoc.X - mouseHorizontalPosition;
                double newTop = deltaV + (double)this.GetValue(Canvas.TopProperty);
                double newLeft = deltaH + (double)this.GetValue(Canvas.LeftProperty);

                // Set new position of object.
                this.SetValue(Canvas.TopProperty, newTop);
                this.SetValue(Canvas.LeftProperty, newLeft);

                // Update position global variables.
                mouseVerticalPosition = MouseLoc.Y;
                mouseHorizontalPosition = MouseLoc.X;                

                this.Opacity = PieceCanBePlaced(e) ? NORMAL_OPACITY : MOVE_OPACITY;                
            }
        }      
       
        private void PickupPiece(System.Windows.Input.MouseButtonEventArgs e)
        {
            this.Cursor = Cursors.Hand;

            mouseVerticalPosition = e.GetPosition(null).Y;
            mouseHorizontalPosition = e.GetPosition(null).X;

            // offset within the piece?
            double yoff = e.GetPosition(this).Y;
            double xoff = e.GetPosition(this).X;           

            //System.Diagnostics.Debug.WriteLine(String.Format("PickupPiece(): {0},{1}", xoff, yoff));

            // Put the mouse at the center of the 0,0 tile            
            double newTop = (double)this.GetValue(Canvas.TopProperty) + yoff - (this.TileSize / 2);
            double newLeft = (double)this.GetValue(Canvas.LeftProperty) + xoff - (this.TileSize / 2);

            // Set new position of object.
            this.SetValue(Canvas.TopProperty, newTop);
            this.SetValue(Canvas.LeftProperty, newLeft);

            this.Opacity = MOVE_OPACITY;
            
            this._parent.PieceBeingDragged = this;

            isMouseCaptured = true;
            this.CaptureMouse();           
        }            

        private void DropPiece(System.Windows.Input.MouseButtonEventArgs e)
        {           
            this.Cursor = Cursors.Arrow;

            Point MouseLoc = e.GetPosition(null);

            isMouseCaptured = false;
            this.ReleaseMouseCapture();
            mouseVerticalDropPosition = mouseVerticalPosition;
            mouseVerticalPosition = -1;
            mouseHorizontalPosition = -1;

            this._parent.PieceBeingDragged = null;

            this.Opacity = NORMAL_OPACITY;            

            // If the piece can't be placed on the board,
            // move it back to its orig. location in the 
            // tilewell.
            if (!PlacePiece(e))                         
                ResetToOriginalPosition();
            
            this.gameBoard.ClearSquareHighlights();

        }
        #endregion

        #region Game Logic Handlers

        private bool PieceCanBePlaced(System.Windows.Input.MouseEventArgs e)
        {
            Point MouseLoc = e.GetPosition(null);
            return this.gameBoard.PieceCanBePlaced(this, MouseLoc.X, MouseLoc.Y);           
        }

        private bool PlacePiece(System.Windows.Input.MouseEventArgs e)
        {
            bool CouldBePlaced = false;
            
            if (PieceCanBePlaced(e))
            {
                CouldBePlaced = this.gameBoard.PlacePiece(this, e);
                this.Placed = CouldBePlaced;
                if (CouldBePlaced && (PiecePlaced != null))
                    PiecePlaced(this);
            }

            return CouldBePlaced;
        }
        #endregion

        #region Transforms
        /// <summary>
        /// Move piece back to original position in the tilewell
        /// </summary>
        public void ResetToOriginalPosition()
        {
            Canvas.SetTop(this, this.OrigTopLeft.Y);
            Canvas.SetLeft(this, this.OrigTopLeft.X);
            this.ClearTransforms();
        }
        /// <summary>
        /// Clear all transforms and reset piece to original orientation
        /// </summary>
        private void ClearTransforms()
        {
            CurrentVectors.Clear();
            foreach (Offset v in OriginalVectors)
            {
                CurrentVectors.Add(v.Clone());
            }

            base_transforms = new TransformGroup();
            TranslateTransform translate = new TranslateTransform();
            translate.X = 0;
            translate.Y = 0;
            base_transforms.Children.Add(translate);
            ScaleTransform scale = new ScaleTransform();
            scale.ScaleX = this.TileSize / TILE_ELEMENT_SIZE_IN_XAML;
            scale.ScaleY = this.TileSize / TILE_ELEMENT_SIZE_IN_XAML;
            base_transforms.Children.Add(scale);
            this.graphic.RenderTransform = base_transforms;

            RedrawTiles();
        }
        /// <summary>
        /// Rotate piece clockwise
        /// </summary>
        public void RotatePieceCW()
        {
            RotatePiece(-1, 1);
        }
        /// <summary>
        /// Rotate piece counter-clockwise
        /// </summary>
        public void RotatePieceCCW()
        {
            RotatePiece(1, -1);
        }
        private void RotatePiece(int mx, int my)
        {                       
            for(int i=0; i<CurrentVectors.Count; i++)
            {
                Offset v = CurrentVectors[i];
                int y = v.Y;
                int x = v.X;
                v.Y = x * mx;
                v.X = y * my;               
            }
            RedrawTiles();
        }

       
        /// <summary>
        /// Flip peice relatvie to x-axis
        /// </summary>
        public void FlipPieceX()
        {
            FlipPiece(-1,1);
        }
        /// <summary>
        /// Flip peice relatvie to y-axis
        /// </summary>
        public void FlipPieceY()
        {
            FlipPiece(1,-1);
        }        
        private void FlipPiece(int mx, int my)
        {
            for (int i = 0; i < CurrentVectors.Count; i++)
            {
                Offset v = CurrentVectors[i];
                v.Y *= my;
                v.X *= mx;
            }
            RedrawTiles();
        }
        #endregion

    }

    public class Offset
    {
        int _x;
        public int X
        {
            get { return _x; }
            set { _x = value; }
        }

        int _y;
        public int Y
        {
            get { return _y; }
            set { _y = value; }
        }        

        public Offset(int x, int y)
        {
            this._x = x;
            this._y = y;
        }

        public Offset Clone()
        {
            return new Offset(this.X, this.Y);
        }
    }



	public static class BlokusPieceFactory
	{     
        private static readonly string[] pieceTypeNames = new string[] { "OneA", "TwoA", "ThreeA", "ThreeB", "FourA", "FourB", "FourC", "FourD", "FourE",
            "FiveA", "FiveB", "FiveC", "FiveD",  "FiveE", "FiveF", "FiveG", "FiveH", "FiveI", "FiveJ", "FiveK", "FiveL", null };

        public static Piece CreatePiece(int pieceType, Color color, TileWell parent)
		{
            string pieceName = BlokusPieceFactory.pieceTypeNames[pieceType];
			if (pieceName != null)
			{                
                return (Piece)Activator.CreateInstance( Type.GetType("Blokus." + pieceName), new object[]{color, parent});				
			}
			return null;
		}
	}
}